# _*_ coding: utf-8 _*_
# @创建时间：2022/12/12 21:59
# @作者：dengqihua
# @名称 : decorators.py
# @描述 : 装饰器

import asyncio
import hashlib
import json
import threading
from functools import wraps

from fastapi.encoders import jsonable_encoder

from app.config.setting import settings
from app.libs.cache.redis import get_redis_conn
from app.libs.logger import aio_logger


def synchronized(func):
    """同步锁装饰器"""
    func.__lock__ = threading.Lock()

    @wraps(func)
    def lock_func(*args, **kwargs):
        with func.__lock__:
            return func(*args, **kwargs)

    return lock_func


def asynchronized(func):
    """异步锁装饰器"""
    func.__lock__ = asyncio.Lock()

    @wraps(func)
    async def lock_func(*args, **kwargs):
        async with func.__lock__:
            return await func(*args, **kwargs)

    return lock_func


def singleton(cls_):
    """单例类装饰器"""

    class wrap_cls(cls_):
        __instance = None

        @synchronized
        def __new__(cls, *args, **kwargs):
            if cls.__instance is None:
                cls.__instance = super().__new__(cls, *args, **kwargs)
                cls.__instance.__init = False
            return cls.__instance

        @synchronized
        def __init__(self, *args, **kwargs):
            if self.__init:
                return
            super().__init__(*args, **kwargs)

    wrap_cls.__name__ = cls_.__name__
    wrap_cls.__doc__ = cls_.__doc__
    wrap_cls.__qualname__ = cls_.__qualname__
    return wrap_cls


def cache_method(prefix=None, cache_time=60):
    """
    方法缓存装饰器
    要缓存的方法返回值必须是可json.dumps
    :param prefix string 缓存key的前缀
    :param cache_time int 缓存过期时间，单位秒
    """

    def cache_decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            cache = await get_redis_conn('cacche')
            r_prefix = f'{prefix}:' if prefix else ''
            # 可以转换为字符串的类型
            to_str_types = (int, str, float, list, tuple, dict)
            param_args = f'{",".join([str(item) for item in args if type(item) in to_str_types])}'
            param_kwargs = ','.join(sorted([f'{k}={v}' for k, v in kwargs.items() if type(v) in to_str_types]))
            param_md5 = hashlib.md5(f'{func.__qualname__}{param_args}{param_kwargs}'.encode()).hexdigest()
            cache_key = f'{settings.project_name}:cache_method:{r_prefix}{func.__name__}:{param_md5}'
            result = await cache.get(cache_key)
            if not result:
                result = await func(*args, **kwargs)
                await cache.setex(name=cache_key, value=json.dumps(jsonable_encoder(result)), time=cache_time)
            else:
                result = json.loads(result)

            return result

        return wrapper

    return cache_decorator


def redis_lock(prefix='', max_timeout=3600):
    """
       生成一个基于Redis的锁装饰器。

       参数:
       - redis_name: Redis连接的名称，默认为'default'。
       - prefix: 锁前缀，可用于区分不同类型的锁，默认为空字符串。
       - max_timeout: 锁的最大超时时间（秒），防止死锁，默认为3600秒。

       返回:
       - lock_decorator: 一个函数装饰器，用于给需要同步执行的异步函数添加Redis锁。
       """
    def lock_decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            to_str_types = (int, str, float, list, tuple, dict)
            real_prefix = f'{prefix}:' if prefix else ''
            param_args = f'{",".join([str(item) for item in args if type(item) in to_str_types])}'
            param_kwargs = ','.join(sorted([f'{k}={v}' for k, v in kwargs.items() if type(v) in to_str_types]))
            param_md5 = hashlib.md5(f'{func.__qualname__}{param_args}{param_kwargs}'.encode()).hexdigest()
            redis_key = f'{settings.project_name}:redis_lock:{real_prefix}{func.__name__}:{param_md5}'

            redis_client = await get_redis_conn()

            # 使用 setnx 命令尝试获取锁。如果键已存在，返回 0（即 False）。
            lock_acquired = await redis_client.setnx(redis_key, 'lock')
            # 如果无法获取锁，则立即抛出异常。
            if not lock_acquired:
                await aio_logger.error(f'{func.__name__}已上锁，执行失败！')
                return False

            try:
                # 设置一个超时时间，以防万一
                await redis_client.expire(redis_key, max_timeout)
                return await func(*args, **kwargs)
            finally:
                await redis_client.delete(redis_key)

        return wrapper

    return lock_decorator
